/** 
 * AGNDisk.java
 */

package agngui.agnmodel;

import java.util.ArrayList;
import java.util.List;

import javax.media.j3d.Appearance;
import javax.media.j3d.Geometry;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.Material;
import javax.media.j3d.QuadArray;
import javax.media.j3d.Transform3D;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

/**
 * Manages the properties of the emission disk.
 */
public class AGNDisk extends AGNObject {
    
    /**
     * AGNDisk
     *
     * Initializes the disk and adds the geometry to the system.
     *
     * @param ri The disk inner radius
     * @param ro The disk outer radius
     * @param n The number of iterations for triangle strip generation.
     * @param modelGeometry Contains the system transform
     */
    public AGNDisk( double ri, double ro, int n, AGNModelGeometry modelGeometry ) {
        super( AGNModelGeometry.EMISSIONDISK, modelGeometry );

        this.Ri = ri;
        this.Ro = ro;    
        this.n  = n;   
        this.modelGeometry = modelGeometry;
 
        geometry = createGeometry();
        setGeometry( geometry[0] );

        setAppearance( createAppearance() );

        transformGroup.addChild( this );
    }

    /** 
     * createGeometry
     * 
     * Creates the disk geometry based on the internal variable states.
     *
     * @return the array of geometry objects composing the disk.
     */
    protected Geometry[] createGeometry() {
        int     diskVertices = 4*n;
        Point3d verts[]      = new Point3d[diskVertices];

        double dTheta = (2 * Math.PI) / n;
        int offset = 0;
        /* Generate points 
		 * 
		 * The first n points are the top disk's inner radius points.
		 * The next n points are the top disk's outer radius points
		 * The next n are the bottom disk's inner radius points
		 * The final n are the bottom disk's outer radius points
		 */
		
        for( double theta = 0; theta < (2 * Math.PI); theta += dTheta ) {
			Point3d inner_point = new Point3d(Ri * Math.cos(theta), Ri * Math.sin(theta), 0);
			Point3d outer_point = new Point3d(Ro * Math.cos(theta), Ro * Math.sin(theta), 0);
			
			verts[offset] = inner_point;
			verts[n + offset] = outer_point;
			verts[2*n + offset] = inner_point;
			verts[3*n + offset] = outer_point;
			offset++;
        }
        
        /* ****** NORMALS ******* */
        Vector3f[] normals = new Vector3f[verts.length];
        Vector3f top_normal = new Vector3f();
        Vector3f bottom_normal = new Vector3f();
        top_normal.cross(new Vector3f(verts[0]), new Vector3f(verts[1]));
        bottom_normal.cross(new Vector3f(verts[2*n]), new Vector3f(verts[2*n+1]));
        bottom_normal.negate();
        
        top_normal.normalize();
        bottom_normal.normalize();
        
		for(int i = 0; i < verts.length; i++) {
			if(i < 2*n) {//inner top
				normals[i] = top_normal;
			} else {//if i < 4*n //outer bottom
				normals[i] = bottom_normal;
			}
		}
 

		List<Point3d> quad_points = new ArrayList<Point3d>();
		List<Vector3f> normal_list = new ArrayList<Vector3f>();

		/* ************************** Create Geometry (quads) **************************** */
        // Top disk
        for( int i = 0; i < n; i++) {
			int bound = i+n+1;//boundary connection, needs to overflow 2*n back to n
			if(bound == 2*n) {
				bound = n;
			}
			int bound2 = i+1;//boundary, needs to overflow n back to 0
			if(bound2 == n) {
				bound2 = 0;
			}
			quad_points.add(verts[i]);
			quad_points.add(verts[i+n]);
			quad_points.add(verts[bound]);
			quad_points.add(verts[bound2]);
			normal_list.add(normals[i]);
			normal_list.add(normals[i+n]);
			normal_list.add(normals[bound]);
			normal_list.add(normals[bound2]);
			//System.out.println((i) + " " + (i+n) + " " + bound + " " + bound2);
        }

        // Bottom disk
        for( int i = 2*n; i < 3*n; i++) {
			int bound = i+n+1;//boundary, needs to overflow 4*n back to 3*n
			if(bound == 4*n) {
				bound = 3*n;
			}
			int bound2 = i+1;//boundary, needs to overflow 3*n back to 2*n
			if(bound2 == 3*n) {
				bound2 = 2*n;
			}
			quad_points.add(verts[i+n]);
			quad_points.add(verts[i]);
			quad_points.add(verts[bound2]);
			quad_points.add(verts[bound]);
			//System.out.println((i+n) + " " + (i) + " " + bound2 + " " + bound);
			normal_list.add(normals[i+n]);
			normal_list.add(normals[i]);
			normal_list.add(normals[bound2]);
			normal_list.add(normals[bound]);
        }

		point_array = quad_points.toArray(new Point3d[0]);
		norm_array = normal_list.toArray(new Vector3f[0]);
		
		GeometryArray geometry_array = new QuadArray(point_array.length, GeometryArray.COORDINATES | GeometryArray.NORMALS);
		geometry_array.setCoordinates(0, point_array);
		geometry_array.setNormals(0, norm_array);
		
        Geometry[] geometryArray = new Geometry[]{ geometry_array };

		generateHandles();
		
        return geometryArray;
    }

    /**
     * createAppearance
     *
     * Specifies the appearance parameters of the disk.
     *
     * @return The Appearance object characterizing an AGN Disk.
     */
    private Appearance createAppearance() {
		setTransparency(0.5f);
        Appearance retVal = getAppearance();
        
		Material mat = retVal.getMaterial();
		mat.setEmissiveColor(new Color3f(1.0f, 1.0f, 1.0f));
		retVal.setMaterial(mat);
		
        return retVal; 
    }

    /**
     * generateHandles
     *
     * Generates the disk's handles (inner and outer radii handles) according to the
     * current state of the disk parameters.
     */
    public void generateHandles() {
        Point3d  points[]           = new Point3d[2];
        Vector3d handleAxes         = new Vector3d( 1.0, 0.0, 0.0 );
        Transform3D objectTransform = new Transform3D();
        
        if( handles == null ) {
            handles = new AGNParameterHandle[points.length];
        }

        transformGroup.getTransform( objectTransform );

        // Inner handle
        points[0] = new Point3d( Ri, 0.0, 0.0 );

        // Outer handle
        points[1] = new Point3d( Ro, 0.0, 0.0 );

        objectTransform.transform( points[0] );
        objectTransform.transform( points[1] );
        objectTransform.transform( handleAxes );

        handles[0] = new AGNLinearParameterHandle( this, points[0], handleAxes, ID_RI ); // Inner radius
        handles[1] = new AGNLinearParameterHandle( this, points[1], handleAxes, ID_RO ); // Outer radius
    }

}
